﻿Imports System.Net
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Threading
Imports System.Text
Imports System.IO.Compression
Imports WindowsLive.Writer.Api

Public Class IMDB
    'http://www.imdb.com/find?s=all&q=King+Arthur

    Dim WithEvents BWorker As New System.ComponentModel.BackgroundWorker

    Event MovieInfoDownloaded(ByVal M As Movie)
    Event SearchResultsDownloaded(ByVal M As MoviesResultset)

    Const table_pattern As String = "<table.*?>(.*?)</table>"
    Const tr_pattern As String = "<tr>(.*?)</tr>"
    'Const td_pattern As String = "<td\sclass=""nm"">(.*?)</td>"
    Const a_pattern As String = "<a.*?href=[""'](?<url>.*?)[""'].*?>(?<name>.*?)</a>"
    Const Code_regex As String = "(?<=\w\w)\d\d\d\d\d\d\d"

    Private Enum SearchType
        Movies = 0
        Details = 1
    End Enum

    Private Structure Results
        Dim ResultType As SearchType
        Dim Result As Object
    End Structure

    Private Structure Arguments
        Dim Search As SearchType
        Dim Parameter As String
        Dim IncludeExactMatches As Boolean
        Dim IncludePartialMatches As Boolean
    End Structure

    Private Structure FindCastStruct
        Public Html As String
        Public P As Movie
    End Structure

    ''' <summary>
    ''' Asynchronous gets the movie details from IMDB and raise an event when downloaded.
    ''' </summary>
    ''' <param name="Code">IMDB movie code.</param>
    ''' <returns></returns>
    Sub GetMovieInfoAsync(ByVal Code As Long)
        BWorker.RunWorkerAsync(New Arguments With {.Search = SearchType.Details, .Parameter = Code})
    End Sub

    Private Function aGetMovieInfo(ByVal Code As Long) As Movie
        'Dim Sw As New Stopwatch
        'Sw.Start()
        'Try

        Dim Url As String = "http://www.imdb.com/title/tt" & Code
        Dim Html As String

        Dim Wc As New WebClient
        'Lets improve speed and save some bandwidth by accepting compressed response :D
        Wc.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate")
        Wc.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 3.5;)")
        Wc.Proxy = PluginHttpRequest.GetWriterProxy()

        Dim Ms As New MemoryStream(Wc.DownloadData(Url))


        If Wc.ResponseHeaders(HttpResponseHeader.ContentEncoding) = "gzip" Then
            Html = New StreamReader(New GZipStream(Ms, CompressionMode.Decompress)).ReadToEnd
        Else
            Html = New StreamReader(Ms).ReadToEnd
        End If

        Dim P As New Movie

        P.ImdbID = Code

        P.Title = Regex.Match(Html, "(?<=<(title)>).*(?=<\/\1>)").ToString()
        P.Year = CInt(Regex.Match(P.Title, "\b\d\d\d\d").ToString)
        'MsgBox(Regex.Match(Html, "(?<=\b(name=""poster"")).*\b[</a>]\b").ToString)
        P.ImageURL = Regex.Match(Regex.Match(Html, "(?<=\b(name=""poster"")).*\b[</a>]\b").ToString, "(?<=\b(src=)).*\b(?=[</a>])").ToString.Replace("""", "").Replace("/></", "")
        Dim RegexRating As String = Regex.Match(Html, "\b\d\W\d/\d\d").ToString
        If RegexRating = "" Then
            P.Rating = 0
        Else
            P.Rating = RegexRating.Split("/".ToCharArray)(0).Replace(".", ",")
        End If
        IIf(IsNumeric(RegexRating), RegexRating, 0)

        'Find all cast of the movie :D

        Const Tr_pattern_1 As String = "<tr\sclass=""odd"">(.*?)</tr>"
        Const Tr_pattern_2 As String = "<tr\sclass=""even"">(.*?)</tr>"
        Const Td_pattern_1 As String = "<td\sclass=""nm"">(.*?)</td>"
        Const Td_pattern_2 As String = "(?<=<td\sclass=""char"">)(.*?)(?=</td>)"

        'Match the table only 1 time
        Dim Table As String = Regex.Match(Html, table_pattern).ToString

        Dim rCast As MatchCollection = Regex.Matches(Table, Tr_pattern_1)
        Dim rCast2 As MatchCollection = Regex.Matches(Table, Tr_pattern_2)

        Dim Cast1 = From M As Match In rCast _
                   Let m1 = Regex.Match(Regex.Match(M.ToString, Td_pattern_1).ToString, a_pattern) _
                   Let m2 = Regex.Match(M.ToString, Td_pattern_2).ToString _
                   Select New Person(Web.HttpUtility.HtmlDecode(m1.Groups("name").ToString), m2.ToString, CLng(Regex.Match(m1.Groups("url").ToString, "(?<=nm).*").ToString.Replace("/", "")))

        Dim Cast2 = From M As Match In rCast2 _
                           Let m1 = Regex.Match(Regex.Match(M.ToString, Td_pattern_1).ToString, a_pattern) _
                           Let m2 = Regex.Match(M.ToString, Td_pattern_2).ToString _
                           Select New Person(Web.HttpUtility.HtmlDecode(m1.Groups("name").ToString), m2.ToString, CLng(Regex.Match(m1.Groups("url").ToString, "(?<=nm).*").ToString.Replace("/", "")))


        Dim Cast As List(Of Person) = Cast1.Union(Cast2).ToList
        'Clean up the character names
        For Each Ps As Person In Cast
            Dim a_patterRegex = Regex.Match(Ps.Character, a_pattern)
            If a_patterRegex.Success Then Ps.Character = a_patterRegex.Groups("name").ToString
        Next

        P.Actors = Cast

        'Get the directors (messy code, I know =D )
        Dim D, W As Integer
        D = If(Html.IndexOf("<h5>Director:</h5>") > 0, Html.IndexOf("<h5>Director:</h5>"), Html.IndexOf("<h5>Directors:</h5>"))
        Dim TryFind As Integer = If(Html.IndexOf("<h5>Writer") > 0, Html.IndexOf("<h5>Writer"), Html.IndexOf("<h5>Writers"))
        W = If(TryFind > 0, TryFind, Html.IndexOf("<h5>Plot Summary:</h5>"))

        'got any director(s) ?
        If Not D <= 0 And Not W <= 0 Then

            Dim rDir As MatchCollection = Regex.Matches(Html.Substring(D, W - D), a_pattern)
            Dim Dir = From M As Match In rDir Where Not M.Groups("name").ToString.Contains("more") _
                      Select New Person(M.Groups("name").ToString, CLng(Regex.Match(M.Groups("url").ToString, "(?<=nm).*").ToString.Replace("/", "")))

            P.Directors = Dir.ToList
        End If

        'Get genres of the movie
        D = 0 : W = 0
        D = Html.IndexOf("<h5>Genre:</h5>")
        'Check if doesnt find genres o.O
        If Not D <= 0 Then
            W = Html.IndexOf("</div>", D)

            Dim rGenres As MatchCollection = Regex.Matches(Html.Substring(D, W - D), a_pattern)

            Dim Gen = From M As Match In rGenres _
                      Select N = M.Groups("name").ToString Where Not N.Contains("more")

            P.Genres = Gen.ToList
        End If


        'Get the Plot Outline
        D = 0 : W = 0

        'Check if is a VideoGame
        Try
            If P.Title.Contains("(VG)") Then
                D = If(Html.IndexOf("<h5>Plot Summary:</h5>") > 0, Html.IndexOf("<h5>Plot Summary:</h5>"), Html.IndexOf("<h5>Tagline:</h5>"))
                If Not D <= 0 Then W = Html.IndexOf("</div>", D)
            Else
                D = If(Html.IndexOf("<h5>Plot Outline:</h5>") > 0, Html.IndexOf("<h5>Plot Outline:</h5>"), Html.IndexOf("<h5>Plot Synopsis:</h5>"))
                If D <= 0 Then D = Html.IndexOf("<h5>Plot Summary:</h5>")
                W = Html.IndexOf("<a class=", D)
                If W <= 0 Then
                    W = Html.IndexOf("</div>", D)
                End If
            End If
            P.Description = Html.Substring(D, W - D).Remove(0, "<h5>Plot Outline:</h5> ".Length)
            'MsgBox(P.Descripcion)
            'My.Computer.FileSystem.WriteAllText("C:\dex.txt", P.Descripcion, False)
        Catch ex As Exception
            P.Description = ""
        End Try

        'Get the movie duration

        Dim RuntimeRegex As String = Regex.Match(Html, "\d\d\d\smin").ToString
        If RuntimeRegex = "" Then RuntimeRegex = Regex.Match(Html, "\d\d\smin").ToString

        P.Runtime = RuntimeRegex

        'D = 0 : W = 0
        'D = Html.IndexOf("<h5>Runtime:</h5>")

        'If Not D <= 0 Then
        '    W = Html.IndexOf("</div>", D)
        '    Dim sRuntime As String = Html.Substring(D, W - D).Remove(0, "<h5>Runtime:</h5>".Length)
        '    P.Runtime = sRuntime.Substring(0, sRuntime.LastIndexOf("min") + 3)
        'End If


        'Get movie available languages
        D = 0 : W = 0
        D = Html.IndexOf("<h5>Language:</h5>")
        If D > 0 Then
            W = Html.IndexOf("</div>", D)

            Dim rLangs As MatchCollection = Regex.Matches(Html.Substring(D, W - D), a_pattern)
            Dim qLang = From M1 As Match In rLangs _
                         Select L = M1.Groups("name").ToString

            P.Languages = qLang.ToArray
        Else
            P.Languages = Nothing
        End If

        'Sw.Stop()
        'MsgBox(Sw.ElapsedMilliseconds)

        Return P
        'Catch ex As Exception
        'Return Nothing
        'MsgBox(ex.ToString)
        'End Try
    End Function

    ''' <summary>
    ''' Return a resultset from IMDB
    ''' </summary>
    ''' <param name="sMovie">Movie title you're searching for.</param>
    ''' <returns></returns>
    Sub SearchMovieAsync(ByVal sMovie As String, Optional ByVal bIncludePartialMatches As Boolean = False, _
                         Optional ByVal bIncludeExactMatches As Boolean = False)
        BWorker.RunWorkerAsync(New Arguments With {.Search = SearchType.Movies, .Parameter = sMovie, .IncludeExactMatches = bIncludeExactMatches, .IncludePartialMatches = bIncludePartialMatches})
    End Sub

    Private Function aSearchMovie(ByVal sMovie As String, Optional ByVal bIncludePartialMatches As Boolean = False, _
                         Optional ByVal bIncludeExactMatches As Boolean = False) As MoviesResultset
        Try

            Dim D, W As Integer
            Dim Url As String = "http://www.imdb.com/find?s=all&q="
            Dim R As New MoviesResultset
            Dim Html As String

            For Each S As String In sMovie.Split(" ".ToCharArray)
                Url += S & "+"
            Next

            Dim Wc As New WebClient
            'Lets improve speed and save some bandwidth by accepting compressed response :D
            Wc.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate")

            Dim Ms As New MemoryStream(Wc.DownloadData(Url))

            If Wc.ResponseHeaders(HttpResponseHeader.ContentEncoding) = "gzip" Then
                Html = New StreamReader(New GZipStream(Ms, CompressionMode.Decompress)).ReadToEnd
            Else
                Html = New StreamReader(Ms).ReadToEnd
            End If

            D = Html.IndexOf("<b>Popular Titles</b>")
            If D <= 0 Then GoTo mParcial
            W = Html.IndexOf("</p>", D)

            Dim Tabla As String = Regex.Match(Html.Substring(D, W - D), table_pattern).ToString
            Dim rPopular As MatchCollection = Regex.Matches(Tabla, tr_pattern)

            For Each Mtr As Match In rPopular
                For Each M As Match In Regex.Matches(Mtr.ToString, a_pattern)
                    If Not M.Groups("name").ToString.Contains("<img") Then
                        R.PopularTitles.Add(New Movie(Regex.Match(M.Groups("url").ToString, Code_regex).ToString, Web.HttpUtility.HtmlDecode(M.Groups("name").ToString)))
                    End If
                Next
            Next

            'Dim qPopular = From Mtr As Match In rPopular _
            '    Let M = Regex.Match(Mtr.ToString, a_pattern) _
            '    Select New Movie(Regex.Match(M.Groups("url").ToString, Codigo_regex).ToString, Web.HttpUtility.HtmlDecode(M.Groups("name").ToString))

            'R.PopularTitles = qPopular.ToList
mParcial:

            If Not bIncludePartialMatches Then GoTo mExactos

            D = Html.IndexOf("Titles (Partial Matches)")
            If D <= 0 Then GoTo mExactos
            W = Html.IndexOf("</p>", D)

            Tabla = String.Empty
            Tabla = Regex.Match(Html.Substring(D, W - D), table_pattern).ToString

            Dim rPartial As MatchCollection = Regex.Matches(Tabla, tr_pattern)
            Dim qPartial = From Mtr As Match In rPartial _
                Let M = Regex.Match(Mtr.ToString, a_pattern) Where Not M.Groups("name").ToString.Contains("<img") _
                Select New Movie(Regex.Match(M.Groups("url").ToString, Code_regex).ToString, Web.HttpUtility.HtmlDecode(M.Groups("name").ToString))

            R.PartialMatches = qPartial.ToList

mExactos:

            If Not bIncludeExactMatches Then GoTo Resultado

            D = Html.IndexOf("Titles (Exact Matches)")
            If D <= 0 Then GoTo Resultado
            W = Html.IndexOf("</p>", D)

            Tabla = String.Empty
            Tabla = Regex.Match(Html.Substring(D, W - D), table_pattern).ToString

            Dim rExact As MatchCollection = Regex.Matches(Tabla, tr_pattern)
            Dim qExactos = From Mtr In rExact _
                Let M = Regex.Match(Mtr.ToString, a_pattern) Where Not M.Groups("name").ToString.Contains("<img") _
                Select New Movie(Regex.Match(M.Groups("url").ToString, Code_regex).ToString, Web.HttpUtility.HtmlDecode(M.Groups("name").ToString))

            R.ExactMatches = qExactos.ToList

Resultado:
            Return R
        Catch ex As Exception
            Return Nothing
            MsgBox(ex.ToString)
        End Try
    End Function

    Private Sub BW_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BWorker.DoWork
        Dim Args As Arguments = e.Argument
        Select Case Args.Search
            Case SearchType.Movies
                Dim r As MoviesResultset = aSearchMovie(Args.Parameter, Args.IncludePartialMatches, Args.IncludeExactMatches)
                e.Result = New Results With {.ResultType = SearchType.Movies, .Result = r}
            Case SearchType.Details
                Dim P As Movie
                P = aGetMovieInfo(Args.Parameter)

                e.Result = New Results With {.ResultType = SearchType.Details, .Result = P}
        End Select
    End Sub

    Private Sub BW_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BWorker.RunWorkerCompleted
        Dim Res As Results = e.Result

        Select Case Res.ResultType
            Case SearchType.Movies
                RaiseEvent SearchResultsDownloaded(Res.Result)
            Case SearchType.Details
                RaiseEvent MovieInfoDownloaded(Res.Result)
        End Select
    End Sub
End Class
